<?php
// $Id$


/**
 * MyApp 封装了应用程序的基本启动流程和初始化操作，并为应用程序提供一些公共服务。
 *
 * 主要完成下列任务：
 * - 初始化运行环境
 * - 提供应用程序入口
 * - 为应用程序提供公共服务
 * - 处理访问控制和用户信息在 session 中的存
 */
class MyApp
{
    /**
     * 应用程序的基本设置
     *
     * @var array
     */
    protected $_app_config;
    
    public    $rbac = null;

    /**
     * 构造函数
     *
     * @param array $app_config
     *
     * 构造应用程序对象
     */
    protected function __construct(array $app_config)
    {
        // #IFDEF DEBUG
        global $g_boot_time;
        //        PLog::log('--- STARTUP TIME --- ' . $g_boot_time, PLog::DEBUG);
        // #ENDIF

        /**
         * 初始化运行环境
         */
        // 禁止 magic quotes
        set_magic_quotes_runtime(0);

        // 处理被 magic quotes 自动转义过的数据
        if (get_magic_quotes_gpc())
        {
            $in = array(& $_GET, & $_POST, & $_COOKIE, & $_REQUEST);
            while (list ($k, $v) = each($in))
            {
                foreach ($v as $key => $val)
                {
                    if (! is_array($val))
                    {
                        $in[$k][$key] = stripslashes($val);
                        continue;
                    }
                    $in[] = & $in[$k][$key];
                }
            }
            unset($in);
        }

        // 设置异常处理函数
        set_exception_handler(array($this, 'exception_handler'));

        // 初始化应用程序设置
        $this->_app_config = $app_config;
        $this->_initConfig();
        Protoss::replaceIni('app_config', $app_config);

        // 设置默认的时区
        date_default_timezone_set(Protoss::getIni('l10n_default_timezone'));

        // 设置 session 服务
        if (Protoss::getIni('runtime_session_provider'))
        {
            Protoss::loadClass(Protoss::getIni('runtime_session_provider'));
        }

        Protoss::setIni('acl_session_key', 'eztheme_rbac');
        // 打开 session
        if (Protoss::getIni('runtime_session_start'))
        {
            session_start();
            // #IFDEF DEBUG
            //            PLog::log('session_start()', PLog::DEBUG);
            //            PLog::log('session_id: ' . session_id(), PLog::DEBUG);
            // #ENDIF
        }

        // 导入类搜索路径
        Protoss::import($app_config['APP_DIR']);

        // 注册应用程序对象
        Protoss::register($this, 'app');
    }

    /**
     * 析构函数
     */
    function __destruct()
    {
        // #IFDEF DBEUG
        global $g_boot_time;
        $shutdown_time = microtime(true);
        $length = $shutdown_time - $g_boot_time;
        $GLOBALS['__PROTOSS_LOG']['throw_time'] = $length;
        //PLog::log("--- SHUTDOWN TIME --- {$shutdown_time} ({$length})sec", PLog::DEBUG);
        // #ENDIF
    }

    /**
     * 返回应用程序类的唯一实例
     *
     * @param array $app_config
     *
     * @return MyApp
     */
    static function instance(array $app_config = null)
    {
        static $instance;
        if (is_null($instance))
        {
            if (empty($app_config))
            {
                die('配置读取失败');
            }
            $instance = new MyApp($app_config);
        }
        return $instance;
    }

    /**
     * 返回应用程序基础配置的内容
     *
     * 如果没有提供 $item 参数，则返回所有配置的内容
     *
     * @param string $item
     *
     * @return mixed
     */
    function config($item = null)
    {
        if ($item)
        {
            return isset($this->_app_config[$item]) ? $this->_app_config[$item] : null;
        }
        return $this->_app_config;
    }

    /**
     * 根据运行时上下文对象，调用相应的控制器动作方法
     *
     * @param array $args
     *
     * @return mixed
     */
    function dispatching(array $args = array())
    {
        // 构造运行时上下文对象
        $context = PContext::instance();

        if (Protoss::ini('i18n_response_charset'))
        {
            header('Content-Type: text/html; charset=' . Protoss::ini('i18n_response_charset'));
        }
        // 获得请求对应的 统一目的地信息
        $request_info = $context->requestInfo('array');        
        
        // 检查是否有权限访问
        $permission   = $this->check_permission();

        if ($permission == false)
        {
            $context->changeRequestInfo('default/permissiondeny');
            $request_info = $context->requestInfo('array');
        }
        // 控制器名
        $controller_name = strtolower($request_info[PContext::REQUEST_CONTROLLER]);
        // 方法名
        $action_name     = strtolower($request_info[PContext::REQUEST_ACTION]);
        $response = $this->controller_execute($controller_name, $action_name, $args);
        if (is_object($response) && method_exists($response, 'execute'))
        {
            // 如果返回结果是一个对象，并且该对象有 execute() 方法，则调用
            $response = $response->execute();
        }
        // TODO 这里还要检查
        elseif ($response instanceof PController_Forward)
        {
            // 如果是一个 PController_Forward 对象，则将请求进行转发
            $response = $this->dispatching($response->args);
        }

        // 其他情况则返回执行结果
        return $response;
    }
    
    protected function check_permission()
    {
        return true;
    }
    
    public function check_permission_rbac()
    {
        // 获得请求对应的 统一目的地信息
        $request_info = PContext::instance()->requestInfo('array');
        // 控制器名
        $controller_name = strtolower($request_info[PContext::REQUEST_CONTROLLER]);
        // 方法名
        $action_name     = strtolower($request_info[PContext::REQUEST_ACTION]);
        $project_access  = Helper_YAML::load(Protoss::getIni('app_config/CONFIG_DIR') . DS . 'acl.yaml.php');
        // 如果存在默认权限设置，则首先使用默认设置，以防止空权限
        if (isset($project_access['_default']))
        {
            $this->acl()->default_access = $project_access['_default'];
        }
        // 如果不存在针对所调用的控制器的权限设定，则采用默认设定
        if (!isset($project_access[$controller_name]))
        {
            if (isset($project_access['_default']))
            {
                $project_access[$controller_name] = $project_access['_default'];
            }else {
                $project_access[$controller_name] = $this->acl()->default_access;
            }
        }
        
        // 如果存在对该方法的权限，则采用设定的权限，否则使用控制器的默认权限。
        if (isset($project_access[$controller_name][$action_name]))
        {
            $access = $project_access[$controller_name][$action_name];
        }else {
            if (isset($project_access[$controller_name]['_default']))
            {
                $this->acl()->default_access = $project_access[$controller_name]['_default'];
            }
            $access = $this->acl()->default_access;
        }
        // 检查权限是否正确
        $permission = $this->acl()->checkPermission($access);
        return $permission;
    }
    
    /**
     * 执行到具体的控制器
     *
     * @param string $controller_name
     * @param string $action_name
     * @param mixed $args
     * @return mixed
     */
    public function controller_execute($controller_name, $action_name, $args = null, $from_direct = null)
    {
        // 文件名
        $filename   = "{$controller_name}_controller.php";
        // 控制器存放目录
        $dir = "{$this->_app_config['APP_DIR']}/controller";
        // 控制器类名
        $class_name = 'Controller';
        $class_name = $controller_name . $class_name;

        // 载入控制器文件
        try
        {
            if (!class_exists($class_name, false))
            {
                Protoss::loadClassFile($filename, array($dir), $class_name);
            }
        }
        catch (PException_FileNotFoundException $ex)
        {
            $response = $this->_on_action_not_defined();
            break;
        }

        // 构造控制器对象
        $controller = new $class_name($this);
        // 重定向信息
        $controller->origin_controller = $from_direct['c'];
        $controller->origin_action     = $from_direct['a'];
        if ($controller->exists_action($action_name))
        {
            // 如果指定动作存在，则调用
            $response = $controller->execute($action_name, $args);
        }
        else
        {
            // 如果指定动作不存在，则尝试调用控制器的 _on_action_not_defined() 函数处理错误
            $response = $controller->_on_action_not_defined($action_name);
            if (is_null($response))
            {
                // 如果控制器的 _on_action_not_defined() 函数没有返回处理结果
                // 则由应用程序对象的 _on_action_not_defined() 函数处理
                $response = $this->_on_action_not_defined();
            }
        }
        return $response;
    }

    /**
     * 将用户数据保存到 session 中
     *
     * @param mixed $user
     * @param mixed $roles
     */
    function changeCurrentUser($user, $roles)
    {
        $user['roles'] = implode(',', Protoss::normalize($roles));
        $_SESSION[Protoss::getIni('acl_session_key')] = $user;
    }

    /**
     * 获取保存在 session 中的用户数据
     *
     * @return array
     */
    function currentUser()
    {
        $key = Protoss::getIni('acl_session_key');
        return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
    }

    /**
     * 获取 session 中用户信息包含的角色
     *
     * @return array
     */
    function currentUserRoles()
    {
        $user = $this->currentUser();
        return isset($user['roles']) ? Protoss::normalize($user['roles']) : array();
    }

    /**
     * 从 session 中清除用户数据
     */
    function cleanCurrentUser()
    {
        unset($_SESSION[Protoss::getIni('acl_session_key')]);
    }



    /**
     * 载入配置文件内容
     *
     * @param array $app_config
     *
     * @return array
     */
    static function loadConfigFiles(array $app_config)
    {
        $ext = !empty($app_config['CONFIG_FILE_EXTNAME'])
               ? $app_config['CONFIG_FILE_EXTNAME'] : 'yaml';
        $cfg = $app_config['CONFIG_DIR'];

        $files = array
        (
            "{$cfg}/acl.{$ext}.php" => 'acl_global',
        );

        $replace = array();
        foreach ($app_config as $key => $value)
        {
            if (!is_array($value)) $replace["%{$key}%"] = $value;
        }

        $config = require(P_DIR . '/_config/default_config.php');
        foreach ($files as $filename => $scope)
        {
            if (!file_exists($filename))
            {
                continue;
            }
            $contents = Helper_YAML::load($filename, $replace);
            if ($scope == 'global')
            {
                $config = array_merge($config, $contents);
            }
            else
            {
                if (!isset($config[$scope]))
                {
                    $config[$scope] = array();
                }
                $config[$scope] = array_merge($config[$scope], $contents);
            }
        }
        return $config;
    }

    /**
     * 初始化应用程序设置
     */
    protected function _initConfig()
    {
        #IFDEF DEBUG
        //        PLog::log(__METHOD__, PLog::DEBUG);
        #ENDIF


        // 没有使用缓存，或缓存数据失效
        $config = self::loadConfigFiles($this->_app_config);
        Protoss::replaceIni($config);
    }
    
    /**
     * 权限控制模块
     *
     * @return Auth_RBAC
     */
    function acl()
    {
        if ($this->rbac == null)
        {
            $this->rbac =  Auth_RBAC::instance(Protoss::ini('acl_session_key'));
        }
        return $this->rbac;
    }


    /**
     * 视图调用未定义的控制器或动作时的错误处理函数
     */
    protected function _on_action_not_defined()
    {
//        dump(PContext::instance());
        exit('控制器或方法未定义');
    }

    /**
     * 默认的异常处理
     */
    function exception_handler(Exception $ex)
    {
        PException::dump($ex);

        dump('如果要改变对异常的处理，请修改文件 "' . __FILE__ . '" 的 exception_handler() 方法');
    }
    
}


/**
 * MyAppException 封装应用程序运行过程中产生的异常
 *
 * @package app
 */
class MyAppException extends PException
{

}
